"""
  Copyright (c) 2024 Northeastern University (China)
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
"""

# 任务：将目标代码划分成已经识别出的组件部分，也就是每个部分是要有.h和c文件组合
# 输入：组件识别报告.json(注意：报告形式如果发生改变，analysis_srcfile函数也要有变化)
# 输出：moudle_dic={
#       application:[]
#       ossa:[.c .h文件相对路径集合]}
# 旧划分思路：
# 1.对于发生重用的路径，要判断如果这个路径只出现再了一个组件中，那这个路径就归属于这个组件，如果出现在好几个组件中，判断这个文件中哪个组件的函数多就归属那个组件
# 2.划分自己独一无二的部分
# 3.对于对应的h文件如果同名的只有1个那就是c对应的h文件，如果有多个那这几个h文件都不判断归属
# 改进划分思路：
# 因为如果仔细划分某个文件是属于哪个oss，可能会有大问题，可能会出现这个文件只有1个文件是抄的就认为属于这个TPL，所以要改进
# 新的划分思路是：
# 按照成分分析结果划分：
# 1.确定TPL组件的边界，通过查看重用路径中包含TPL名称或者TPL.h所在子目录或者TPL的readme,copyright,license所在子目录就是这个TPL的边界
# 2.边界内，也就是子目录内所有文件包括.h认为是这个组件的文件 ，也就是说认为这个子目录及其包含的文件来自于该TPL，将.h文件划分进去，不是只划分有重用发生的文件
# 3.如果一个TPL所有的重用路径都不包含上述几个要素，那就按照重用发生数对子目录降序排序，挑选发生重用最多的子目录，其下的文件认为是该TPL模块的组成部分

import difflib
import re
import traceback
import json
import os
from collections import defaultdict

from src.common_utils.utils_file import read_jl
similar_theta = 0.9

class PartitionModule:


    def analysis_srcfile(self,report_path,module_dic):
        """
        读取鸿蒙项目的组件识别报告
        {"name": "foliojs@@font-manager_sig",
        "path": ["\\skia\\src\\ports\\SkFontMgr_win_dw.cpp", "\\skia\\src\\ports\\SkRemotableFontMgr_win_dw.cpp"]}
        需要特别注意：
        组件识别报告中只有源文件没有h文件，需要找h文件，目前找.h文件是找和c文件同路径下同名.h
       """
        if not os.path.exists(report_path):
            return []
        with open(report_path, 'r') as json_file:
            line = json_file.readline()
            while line:
                try:
                    item = json.loads(line)
                    paths=item['path'][:]
                    absolute_path=[]
                    ossname=item['name']
                    for path in paths:
                        path = os.path.normpath(path.strip())
                        path=os.path.join(target_path,path)
                        absolute_path.append(path)
                        point = path.rfind(".")
                        head_path = path[:point] + ".h"
                        if path in module_dic[0]['path']:
                            module_dic[0]['path'].remove(path)
                            module_dic[0]['total']= module_dic[0]['total']-1
                        if head_path in module_dic[0]['path']:
                            module_dic[0]['path'].remove(head_path)
                            absolute_path.append(head_path)
                            module_dic[0]['total'] = module_dic[0]['total'] - 1
                    oss_dic={
                        'name':ossname,
                        'path':absolute_path,
                        'total':len(absolute_path)
                    }
                    module_dic.append(oss_dic)
                except Exception as e:
                    print(f"json load error, line={line}, msg={str(e)}")
                    traceback.print_exc()
                line = json_file.readline()
        return module_dic

    def create_reuse_path_index(self, components,target_allfiles,target_path,head_files,module_dic):
        """
        生成重用路径索引
        {
        ‘path1’：[oss1,oss2]
        }
        寻找头文件策略：找和文件同名.h文件如果只有一个这样的文件就认为是对应的.h文件
        如果对应有不止一个同名.h文件那这几个文件都不做处理，也就是不划分到任何模块中

        @param components:识别出的组件成分（会包含重用路径信息）
        @param target_allfiles:目标代码所有的文件
        @param target_path:目标代码路径
        @param head_files:目标代码的头文件
        @param module_dic:模块字典
        @return:reuse_path_index,module_dic
        """
        reuse_path_index = defaultdict(list)  # 重用路径索
        for item in components:
            try:
                paths=item['path']
                ossname=item['name']
                for rel_path,frequency in paths.items():
                    rel_path = os.path.normpath(rel_path.lstrip('\\'))
                    abs_path=os.path.normpath(os.path.join(target_path, rel_path))
                    filename = os.path.basename(abs_path)
                    point = filename.rfind(".")
                    head_name=filename[:point]+".h"
                    if len(head_files[head_name])>=1:
                        for headpath in head_files[head_name]:
                            if headpath in module_dic[0]['path']:
                                module_dic[0]['path'].remove(headpath)
                        if len(head_files[head_name])==1:
                            reuse_path_index[head_files[head_name][0]].append((ossname,frequency))
                            print(f"{ossname}:{head_files[head_name][0]}")
                    head_path = abs_path[:point] + ".h"
                    if abs_path in target_allfiles:
                       reuse_path_index[abs_path].append((ossname,frequency))
                       if abs_path in module_dic[0]['path']:
                            module_dic[0]['path'].remove(abs_path)
                    # if head_path in target_allfiles:
                    #   reuse_path_index[head_path].append(ossname)
            except Exception as e:
                print(f"json load error, , msg={str(e)}")
                traceback.print_exc()
        # Sort the values of each key by the second element of the tuple (weight) in descending order
        for key in reuse_path_index:
            reuse_path_index[key] = sorted(reuse_path_index[key], key=lambda x: x[1], reverse=True)
        module_dic[0]['total']=len(module_dic[0]['path'])
        return reuse_path_index,module_dic
    def partition_component_path_enhence(self,components,module_dic):
       """
       改进后的划分思路
       对于可信度为0的组件，就认为抄袭函数最多的子目录是组件的边界
       对于路径中包含组件名称的组件，就认为相应的子目录是边界
       对于目标代码包含组件.h文件以及配置文件的情况，认为相应的子目录就是边界
       除上述之外的子目录认为是程序代码边界
       这个划分，没有把头文件划分进去，而且只是划分了发生重用的文件
       """
       moudle=[] #组件边界路径，就是那些子目录可能是TPL的边界，划分程序代码时不应该再划分这些边界
       #先划分组件部分
       for component in components:
           ossname = component['name']
           paths = component['path']
           if component['reliability']==0:#如果可信 度是0，那就取抄袭函数最多的子目录下的文件作为TPL的边界
               dirs=defaultdict(int)
               for key,value in paths.items():
                    dirname=os.path.dirname(key)
                    count=len(value)
                    dirs[dirname]+=count
               sorteddirs=dict(sorted(dirs.items(),key=lambda item: item[1], reverse=True))
               print(f'这是可信度为0的组件，划分你模块，降序排序每个最小子目录下的重用函数数量:{sorteddirs}')
               rel_path = []
               for moudlestr in sorteddirs:
                   print(f'这是重用函数数量最多的，子目录：{moudlestr}')
                   if moudlestr not in moudle:
                        moudle.append(moudlestr)
                   for key in paths:
                       if moudlestr == os.path.dirname(key):
                           rel_path.append(key)
                   break
               if len(rel_path) != 0:
                   oss_dic = {
                       'name': ossname,
                       'path': rel_path,
                       'total': len(rel_path)
                   }
                   module_dic.append(oss_dic)
           else:#划分有可信度的组件
               verify=component['verify']
               for path,desc in verify:
                   if desc=="path contain oss name":
                       index=ossname.rfind("_")
                       reponame=ossname[:index]
                       print(f'这是可以在路径中找到OSS名称的组件模块划分，组件名称是:{reponame}')
                       pathnode=path.split("\\")
                       moudlestr=""
                       for i in range(0,len(pathnode)):
                           node = pathnode[i].lower().replace(".cc", "")
                           node = node.replace(".cpp", "")
                           node = node.replace(".c", "")
                           matcher = difflib.SequenceMatcher(None,node, reponame) #需要找到到底是路径中哪个节点和oss名称相似，要确定子目录
                           similarity_ratio = matcher.ratio()
                           if similarity_ratio >= similar_theta:  # 正确检测
                               moudlestr = os.path.join(moudlestr, pathnode[i])
                               break
                           else:
                               moudlestr=os.path.join(moudlestr,pathnode[i])
                       rel_path = []
                       print(f'这是确定的子目录：{moudlestr}')
                       if moudlestr not in moudle:
                            moudle.append(moudlestr)
                       for key in paths:
                           if moudlestr in key:
                               rel_path.append(key)
                       if len(rel_path)!=0:
                           oss_dic = {
                               'name': ossname,
                               'path': rel_path,
                               'total': len(rel_path)
                           }
                           module_dic.append(oss_dic)
                           break
                   else:
                       rel_path = []
                       moudlestr=os.path.dirname(path)
                       print(f'这是其他情况，如包含oss名.h文件，或者是copying文件等，这种情况会直接给出这个文件的路径，所以我们划分的时候，直接找到该文件所在子目录：{moudlestr}')
                       if moudlestr not in moudle:
                            moudle.append(moudlestr)
                       for key in paths:
                           if moudlestr in key:
                               rel_path.append(key)
                       if len(rel_path) != 0:
                           oss_dic = {
                               'name': ossname,
                               'path': rel_path,
                               'total': len(rel_path)
                           }
                           module_dic.append(oss_dic)
                           break
       #划分程序部分
       targetmoudle_path=module_dic[0]['path']
       rel_path=[]
       for key in targetmoudle_path:
           if all(item not in key for item in moudle):
               rel_path.append(key)
       if len(rel_path)!=0:
           module_dic[0]['path']=rel_path
           module_dic[0]['total'] = len(rel_path)
       else:
           module_dic.pop(0)
       return module_dic

    def partition_component_path_enhence_modify(self, components, module_dic):
        """
        改进后的划分思路
        对于可信度为0的组件，就认为抄袭函数最多的子目录是组件的边界
        对于路径中包含组件名称的组件，就认为相应的子目录是边界
        对于目标代码包含组件.h文件以及配置文件的情况，认为相应的子目录就是边界
        除上述之外的子目录认为是程序代码边界
        修改处：我会划分.h文件，以及直接确定子目录下所有文件都归属一个组件
        """
        moudle = []  # 组件边界路径，就是那些子目录可能是TPL的边界，划分程序代码时不应该再划分这些边界
        # 先划分组件部分
        for component in components:
            ossname = component['name']
            paths = component['path']
            targetcodeallpath=module_dic[0]['path']
            if component['reliability'] == 0:  # 如果可信 度是0，那就取抄袭函数最多的子目录下的文件作为TPL的边界
                dirs = defaultdict(int)
                for key, value in paths.items():
                    dirname = os.path.dirname(key)
                    count = len(value)
                    dirs[dirname] += count
                sorteddirs = dict(sorted(dirs.items(), key=lambda item: item[1], reverse=True))
                print(f'{ossname}:这是可信度为0的组件，划分你模块，降序排序每个最小子目录下的重用函数数量:{sorteddirs}')
                rel_path = []
                for moudlestr in sorteddirs:
                    print(f'这是重用函数数量最多的，子目录：{moudlestr}')
                    if moudlestr not in moudle:
                        moudle.append(moudlestr)
                    for key in targetcodeallpath:
                        if moudlestr == os.path.dirname(key):
                            rel_path.append(key)
                            module_dic[0]['path'].remove(key)
                    break
                if len(rel_path) != 0:
                    oss_dic = {
                        'name': ossname,
                        'path': rel_path,
                        'total': len(rel_path)
                    }
                    module_dic.append(oss_dic)
            else:  # 划分有可信度的组件
                verify = component['verify']
                for path, desc in verify:
                    if desc == "path contain oss name":
                        index = ossname.rfind("_")
                        reponame = ossname[:index]
                        print(f'{ossname}:{path}这是可以在路径中找到OSS名称的组件模块划分，组件名称是:{reponame}')
                        pathnode = path.split("\\")
                        moudlestr = ""
                        for i in range(0, len(pathnode)):
                            node = pathnode[i].lower().replace(".cc", "")
                            node = node.replace(".cpp", "")
                            node = node.replace(".c", "")
                            matcher = difflib.SequenceMatcher(None, node, reponame)  # 需要找到到底是路径中哪个节点和oss名称相似，要确定子目录
                            similarity_ratio = matcher.ratio()
                            if similarity_ratio >= similar_theta:  # 正确检测
                                moudlestr = os.path.join(moudlestr, pathnode[i])
                                print(f'这是最终会确定的子目录：{moudlestr}')
                                break
                            else:
                                moudlestr = os.path.join(moudlestr, pathnode[i])
                                print(f'这是确定的子目录的过程：{moudlestr}')
                        rel_path = []
                        print(f'这是确定的子目录：{moudlestr}')
                        if moudlestr not in moudle:
                            moudle.append(moudlestr)
                        for key in targetcodeallpath:
                            if moudlestr in key:
                                rel_path.append(key)
                                module_dic[0]['path'].remove(key)
                        if len(rel_path) != 0:
                            oss_dic = {
                                'name': ossname,
                                'path': rel_path,
                                'total': len(rel_path)
                            }
                            module_dic.append(oss_dic)
                            break
                    else:
                        rel_path = []
                        moudlestr = os.path.dirname(path)
                        print(
                            f'{ossname}:{path}:这是其他情况，如包含oss名.h文件，或者是copying文件等，这种情况会直接给出这个文件的路径，所以我们划分的时候，直接找到该文件所在子目录：{moudlestr}')
                        if moudlestr not in moudle:
                            moudle.append(moudlestr)
                        for key in targetcodeallpath:
                            if moudlestr in key:
                                rel_path.append(key)
                                module_dic[0]['path'].remove(key)
                        if len(rel_path) != 0:
                            oss_dic = {
                                'name': ossname,
                                'path': rel_path,
                                'total': len(rel_path)
                            }
                            module_dic.append(oss_dic)
                            break
        # 划分程序部分
        print(f'之前已经分析过的，哪些子目录已经是有归属组件的：')
        for key in moudle:
            print(key)
        targetmoudle_path = module_dic[0]['path']
        rel_path = []
        for key in targetmoudle_path:
            if all(item not in key for item in moudle):
                rel_path.append(key)
        if len(rel_path) != 0:
            module_dic[0]['path'] = rel_path
            module_dic[0]['total'] = len(rel_path)
        else:
            module_dic.pop(0)
        return module_dic
    def partition_component_path(self,reuse_path_index,module_dic):
        """
        划分每个路径属于哪个组件模块
        如果一个重用路径只出现在一个组件中，这个路径就是这个组件的
        如果一个路径出现在多个组件中，就看这个路径下哪个组件重用的多，也就是在哪个组件中这个路径出现的频率多，如果一样多，就认为同时属于这两个组件
        @param reuse_path_index: 重用路径索引
        @param module_dic: 模块字典
        @return:
        """

        def get_weight(item):
            return item[1]

        oss_path = defaultdict(list)
        for reuse_path, osses in reuse_path_index.items():
            if len(osses) > 1:
                # 找到所有复用函数最多的oss
                max_value_data = [item for item in osses if item[1] == osses[0][1]]
                for max_oss in max_value_data:
                    oss_path[max_oss[0]].append((reuse_path, 0))
                    print(f"出现频率相同{max_oss}")
            elif len(osses)==1:
                oss_path[osses[0][0]].append((reuse_path, 1))
            else:
                pass
        for key in oss_path:
            oss_path[key] = sorted(oss_path[key], key=get_weight, reverse=True)
        # Create a new dictionary without the weight
        oss_path_no_weight = {key: [item[0] for item in value] for key, value in oss_path.items()}
        for oss, paths in oss_path_no_weight.items():
            module = {
                'name': oss,
                'path': paths,
                'total': len(paths)
            }
            if module not in module_dic:
                module_dic.append(module)
        return module_dic







    def get_target_allfiles(self,target_path):
        """

        :param target_path: 目标软件的路径
        :return: target_allfiles目标软件中所有的文件
        head_files:所有的头文件
        module_dic：模块字典初始化
        """
        possible = (".c", ".cc", ".cpp",".h")
        target_allfiles = []
        target_src_repo = target_path.split('\\')[-1]
        head_files=defaultdict(list)
        for root, dirs, files in os.walk(target_path):
            for file in files:
                file_path = os.path.relpath(os.path.join(root, file),target_path)#取相对路径
                if file.endswith(possible):
                    target_allfiles.append(file_path)
                if file.endswith(".h"):
                    head_files[ os.path.basename(file_path)].append(file_path)
        module_dic=[{
            'name':'target_code',
            'path':target_allfiles.copy(),
            'total':len(target_allfiles)
        }]

        return target_allfiles,head_files,module_dic

    # def main(self,components,target_path):
    #     target_allfiles,head_files,module_dic=self.get_target_allfiles(target_path)
    #     reuse_path_index,module_dic=self.create_reuse_path_index(components,target_allfiles,target_path,head_files,module_dic)
    #     print(f"索引：{reuse_path_index}")
    #     module_dic=self.partition_component_path(reuse_path_index,module_dic)
    #     return module_dic

    def main(self, components, target_path):
        target_allfiles, head_files, module_dic = self.get_target_allfiles(target_path)
        module_dic = self.partition_component_path_enhence_modify(components, module_dic)
        return module_dic

if __name__ == "__main__":
    report_path = input("reportpath:")
    target_path=input("targetpath:")
    partitionModule=PartitionModule()
    components = read_jl(report_path)
    module_dic=partitionModule.main(components,target_path)
    fres = open("result_partition_modul.json",'w')
    for dic in module_dic:
        json_str = json.dumps(dic)
        fres.write(json_str + '\n')